package com.gmrz.uaf.db.dao;

import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.service.challengestore.UAFChallengeStoreServiceProvider;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.AAID;
import com.gmrz.uaf.protocol.v1.schema.AuthenticatorSpec;
import com.gmrz.util.CryUtil;
import com.gmrz.util.db.DBUtil;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public class BaseAuthSpecDAO
        implements AuthenticatorSpecDAO {
    private static final Logger LOG = LogManager.getLogger(BaseAuthSpecDAO.class);
    private Connection conn_;
    private static final String _SQL_SELECT_ALL = "SELECT metadata_id, aaid, description,auth_version,json_data,status,auth_type FROM t_auth_metadata ";
    private static final String _SQL_WHERE_AAID_AND_STATUS_ACTIVE = " where aaid=? and status=" + Constants.AuthenticatorSpecStatus.ACTIVE.getStatus();
    private static final String _SQL_WHERE_ID_STATUS_ACTIVE = " where metadata_id=? and status=" + Constants.AuthenticatorSpecStatus.ACTIVE.getStatus();
    private static final String _SQL_WHERE_STATUS_ACTIVE = " where  status=" + Constants.AuthenticatorSpecStatus.ACTIVE.getStatus();

    @AssistedInject
    public BaseAuthSpecDAO(@Assisted Connection conn_) {
        this.conn_ = conn_;
    }

    public BaseAuthSpecDAO() {

    }

    public List<AuthenticatorSpec> list() throws DAOException {
        List<AuthenticatorSpec> list = getCacheSpecList();
        if (null != list && !list.isEmpty()) {
            return list;
        }
        String q = _SQL_SELECT_ALL + _SQL_WHERE_STATUS_ACTIVE + " and protocol='uaf'";
        ResultSet rs = null;
        PreparedStatement s = null;
        List<AuthenticatorSpec> retval = new ArrayList<AuthenticatorSpec>();
        try {
            s = this.conn_.prepareStatement(q);
            rs = s.executeQuery();
            List<String> cacheSpecList = new ArrayList<String>();
            while (rs.next()) {
                AuthenticatorSpec spec = readSpec(rs);
                retval.add(spec);
                cacheSpecList(rs, cacheSpecList);
            }
            if(cacheSpecList.size() > 0) {
                UAFChallengeStoreServiceProvider challengeProvider = GuiceUtil.getProcessorInjector().getInstance(UAFChallengeStoreServiceProvider.class);
                String[] strs = new String[1];
                challengeProvider.get().sadd(Constants.UAP_SPEC_LIST, cacheSpecList.toArray(strs));
            }
        } catch (Exception sqle) {
            LOG.error("The following SQL statement failed to execute:\n {}", q);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        } finally {
            DBUtil.close(rs);
            DBUtil.close(s);
        }

        return retval;
    }


    public AuthenticatorSpec getBySpecID(String specid) throws DAOException {
        AuthenticatorSpec spec = getCacheSpecById(specid);
        if (null != spec) {
            return spec;
        }
        String q = _SQL_SELECT_ALL + _SQL_WHERE_ID_STATUS_ACTIVE+ " and protocol='uaf'";
        ResultSet rs = null;
        PreparedStatement s = null;
        try {
            s = this.conn_.prepareStatement(q);
            s.setString(1, specid);
            rs = s.executeQuery();
            if (rs.next()) {
                spec = readSpec(rs);
                if (null != spec) {
                    cacheSpec(rs);
                }
                return spec;
            }
            return null;
        } catch (SQLException sqle) {
            LOG.error("The following SQL statement failed to execute:\n {}", q);
            LOG.error(sqle);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        } finally {
            DBUtil.close(rs);
            DBUtil.close(s);
        }
    }

    public AuthenticatorSpec getByAAID(String aaid) throws DAOException {
        AuthenticatorSpec spec = getCacheSpecByAAID(aaid);
        if (null != spec) {
            return spec;
        }
        String q = "SELECT metadata_id, aaid, description, auth_version,json_data, status , auth_type FROM t_auth_metadata" + _SQL_WHERE_AAID_AND_STATUS_ACTIVE;
        ResultSet rs = null;
        PreparedStatement s = null;
        try {
            s = this.conn_.prepareStatement(q);
            s.setString(1, aaid);
            rs = s.executeQuery();
            if (rs.next()) {
                spec = readSpec(rs);
                if (null != spec) {
                    cacheSpec(rs);
                }
                return spec;
            }
            return null;
        } catch (SQLException sqle) {
            LOG.error("The following SQL statement failed to execute:\n {}", q);
            LOG.error(sqle);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        } finally {
            DBUtil.close(rs);
            DBUtil.close(s);
        }
    }


    private AuthenticatorSpec readSpec(ResultSet rs) throws DAOException {
        AuthenticatorSpec spec = null;

        Gson gson = UAFSchemaBuilder.getGson();
        try {
            String jsondata = rs.getString("json_data");
            String authType = rs.getString("auth_type");
            jsondata = decryptMetaData(jsondata, authType);
            spec = gson.fromJson(jsondata, AuthenticatorSpec.class);

            if (spec != null) {
                spec.setID(rs.getString("metadata_id"));
                spec.setAAID(new AAID().parse(rs.getString("aaid")));
                spec.setDescription(rs.getString("description"));
                spec.setSpecStatus(Constants.AuthenticatorSpecStatus.forStatus(rs.getInt("status")));
            }
        } catch (Exception sqle) {
            LOG.error("Failed to read AuthenticatorSpec from the database", sqle);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        }
        return spec;
    }

    private void cacheSpec(ResultSet rs) throws DAOException {
        HashMap<String, String> specMap = new HashMap<String, String>();
        try {
            String jsondata = rs.getString("json_data");
            String authType = rs.getString("auth_type");
            jsondata = decryptMetaData(jsondata, authType);
            specMap.put("json_data", jsondata);
            specMap.put("metadata_id", rs.getString("metadata_id"));
            specMap.put("aaid", rs.getString("aaid"));
            specMap.put("description", rs.getString("description"));
            specMap.put("status", rs.getString("status"));
            UAFChallengeStoreServiceProvider challengeProvider = GuiceUtil.getProcessorInjector().getInstance(UAFChallengeStoreServiceProvider.class);
            String aaidKey = Constants.UAP_SPEC_AAID + rs.getString("aaid");
            challengeProvider.get().putvalue(aaidKey, specMap);
            String idKey = Constants.UAP_SPEC_ID + rs.getString("metadata_id");
            challengeProvider.get().putvalue(idKey, specMap);
        } catch (Exception sqle) {
            LOG.error("Failed to set AuthenticatorSpec to cache", sqle);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        }
    }

    private AuthenticatorSpec getCacheSpecBykey(String key) throws DAOException {
        AuthenticatorSpec spec = null;
        try {
            UAFChallengeStoreServiceProvider challengeProvider = GuiceUtil.getProcessorInjector().getInstance(UAFChallengeStoreServiceProvider.class);
            String[] f = new String[]{"json_data", "metadata_id", "aaid", "description", "status"};
            List<String> valueList = challengeProvider.get().getvalue(key, f);
            String jsonData = valueList.get(0);
            String metadataId = valueList.get(1);
            String aaidStr = valueList.get(2);
            String description = valueList.get(3);
            String status = valueList.get(4);
            Gson gson = UAFSchemaBuilder.getGson();
            spec = gson.fromJson(jsonData, AuthenticatorSpec.class);
            if (spec != null) {
                spec.setID(metadataId);
                spec.setAAID(new AAID().parse(aaidStr));
                spec.setDescription(description);
                spec.setSpecStatus(Constants.AuthenticatorSpecStatus.forStatus(Integer.parseInt(status)));
            }
        } catch (Exception sqle) {
            LOG.error("Failed to set AuthenticatorSpec to cache", sqle);
            throw new DAOException(UAFErrorCode.DB_QUERY_METADATA_FAILED, sqle);
        }
        return spec;
    }

    private AuthenticatorSpec getCacheSpecByAAID(String aaid) throws DAOException {
        String key = Constants.UAP_SPEC_AAID + aaid;
        return getCacheSpecBykey(key);
    }

    private AuthenticatorSpec getCacheSpecById(String id) throws DAOException {
        String key = Constants.UAP_SPEC_ID + id;
        return getCacheSpecBykey(key);
    }

    private String decryptMetaData(String metaDataCipherText, String authType) {
        JsonParser parser = new JsonParser();
        JsonObject jsonObject = parser.parse(metaDataCipherText).getAsJsonObject();
        try {
            JsonArray jsonArray = jsonObject.getAsJsonArray("attestationRootCertificates");
            if (null != jsonArray && jsonArray.size() > 0) {
                String str = jsonArray.get(0).getAsString();
                if(str.contains("*")) {
                    Iterator it = jsonArray.iterator();
                    while (it.hasNext()) {
                        it.next();
                        it.remove();
                    }
                    String data = CryUtil.decryptMetaData(str);
                    Gson gson = new Gson();
                    String s1 = gson.toJson(data);
                    //if (authType.compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) < 0) {
                        jsonArray.add(parser.parse(s1));
                    //}
                    jsonObject.addProperty("attestationRootCertificatesStr", data);
                }else {
                    jsonObject.addProperty("attestationRootCertificatesStr", str);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return new Gson().toJson(jsonObject);
    }

    private void cacheSpecList(ResultSet rs, List<String> cacheSpecList) {
        Gson gson = new Gson();
        try {

            HashMap<String, String> specMap = new HashMap<String, String>();
            String jsondata = rs.getString("json_data");
            String authType = rs.getString("auth_type");
            jsondata = decryptMetaData(jsondata, authType);
            specMap.put("json_data", jsondata);
            specMap.put("metadata_id", rs.getString("metadata_id"));
            specMap.put("aaid", rs.getString("aaid"));
            specMap.put("description", rs.getString("description"));
            specMap.put("status", rs.getString("status"));
            String json = gson.toJson(specMap);
            cacheSpecList.add(json);


        } catch (Exception e) {

        }
    }

    private List<AuthenticatorSpec> getCacheSpecList() {
        List<AuthenticatorSpec> list = new ArrayList<AuthenticatorSpec>();
        try {
            UAFChallengeStoreServiceProvider challengeProvider = GuiceUtil.getProcessorInjector().getInstance(UAFChallengeStoreServiceProvider.class);
            Set<String> set = challengeProvider.get().smembers(Constants.UAP_SPEC_LIST);
            if (null != set) {
                Gson gson = new Gson();
                Iterator<String> it = set.iterator();
                while (it.hasNext()) {
                    String str = it.next();
                    HashMap specMap = gson.fromJson(str, HashMap.class);
                    if (null != specMap) {
                        String json_data = (String) specMap.get("json_data");
                        String metadata_id = (String) specMap.get("metadata_id");
                        String aaid = (String) specMap.get("aaid");
                        String description = (String) specMap.get("description");
                        String status = (String) specMap.get("status");
                        Gson gson1 = UAFSchemaBuilder.getGson();
                        AuthenticatorSpec spec = gson1.fromJson(json_data, AuthenticatorSpec.class);
                        if (spec != null) {
                            spec.setID(metadata_id);
                            spec.setAAID(new AAID().parse(aaid));
                            spec.setDescription(description);
                            spec.setSpecStatus(Constants.AuthenticatorSpecStatus.forStatus(Integer.parseInt(status)));
                            list.add(spec);
                        }
                    }
                }
            }
        } catch (Exception e) {

        }
        return list;
    }

}
